//
//  SLPagerView.m
//  SLPagerView
//
//  Created by l.t.zero on 2022/1/7.
//

#import "SLPagerView.h"
#import "SLPagerHelper.h"
#import <objc/runtime.h>
#import <objc/message.h>

NSString *const __sl_resue_identifier = @"__sl_resue_identifier_7ahdg12sdx";
const char *const __sl_associated_item = "__sl_resue_assiciation_item_7ahdg12sdx";

static inline void _pager_view_bind_item(id view, id<SLPagerItem> item){
    objc_setAssociatedObject(view, __sl_associated_item, item, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
};
static inline id<SLPagerItem> _pager_view_item_from(id view){
    return objc_getAssociatedObject(view, __sl_associated_item);
};

@interface SLPagerView()<UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UICollectionViewDataSourcePrefetching, SLPagerReusePoolDelegate>

@property (nonatomic, strong) UICollectionView *collectionView;
@property (nonatomic, assign) NSInteger currentIndex;
@property (nonatomic, assign) BOOL disablePrefetch;
@property (nonatomic, strong) NSArray<id<SLPagerItem>> *dataSource;

@property (nonatomic, strong) NSMutableSet *registerSet;
@property (nonatomic, strong) SLPagerReusePool *reusePool;
@property (nonatomic, weak) UICollectionViewLayout *layout;
//@property (nonatomic, assign) BOOL isScrolling;
@property (nonatomic, assign) BOOL isReloading;
//@property (nonatomic, assign) NSInteger currentIndex;

@property (nonatomic, strong) SLPagerObject *prevObj;///记录上次通知的内容

@end

@implementation SLPagerView{
    NSArray *_dataSource;
}

- (instancetype)initWithFrame:(CGRect)frame layout:(nonnull UICollectionViewLayout *)layout{
    
    if (self = [super initWithFrame:frame]){
        _currentIndex = 0;
        _prefetchingEnabled = YES;
        _registerSet = [NSMutableSet new];
        _reusePool = [SLPagerReusePool new];
        _reusePool.delegate = self;
        self.layout = layout;
        [self addSubview:self.collectionView];
    }
    
    return self;
}

- (void)layoutSubviews{
    [super layoutSubviews];
    self.reusePool.preferredFrame = self.bounds;
    if (!CGRectEqualToRect(self.collectionView.frame, self.bounds)){
        [self changeToFrame:self.bounds];
//        [self.collectionView.col reloadData];
    }
}
- (void)setEnableInfinateScroll:(BOOL)enableInfinateScroll{
    self.reusePool.enableInfinateScroll = (_enableInfinateScroll = enableInfinateScroll);
}

#pragma mark --
- (UICollectionView *)collectionView{
    if(!_collectionView){
        _collectionView = [[UICollectionView alloc] initWithFrame:self.bounds collectionViewLayout:[self layout]];
        _collectionView.backgroundColor = [UIColor clearColor];
        _collectionView.dataSource = self;
        _collectionView.delegate = self;
        _collectionView.scrollsToTop = NO;
        _collectionView.contentInset = UIEdgeInsetsZero;
//        _collectionView.pagingEnabled = YES;
        [_collectionView setDelaysContentTouches:YES];
        [_collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:__sl_resue_identifier];
        if (@available(iOS 10.0, *)) {
            _collectionView.prefetchingEnabled = NO;
            _collectionView.prefetchDataSource = self;
        } else {
            // Fallback on earlier versions
        }
        if (@available(iOS 11.0, *)) {
            _collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
        } else {
            // Fallback on earlier versions
        }
    }
    return _collectionView;
}
#pragma mark - dataSource
- (void)registerClassForReuse:(nullable Class)cellClass identifier:(NSString *)identifier{
    [self.reusePool registerClass:cellClass forCellWithReuseIdentifier:identifier];
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
    return self.dataSource.count;
}

- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath{
    UIView *view = [self.reusePool reuseViewFor:indexPath];
    
    if (view.superview == cell.contentView) {
//        [view removeFromSuperview];
        
        if([self.delegate respondsToSelector:@selector(sl_pagerView:didEndDisplay:atIndexPath:withData:)]) {
            [self.delegate sl_pagerView:self didEndDisplay:view atIndexPath:indexPath withData:_pager_view_item_from(view)];
        }
    }
}

- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath{
    UIView *view = [self.reusePool reuseViewFor:indexPath];
    if ([self.delegate respondsToSelector:@selector(sl_pagerView:willDisplay:atIndexPath:withData:)]) {
        [self.delegate sl_pagerView:self willDisplay:view atIndexPath:indexPath withData:_pager_view_item_from(view)];
    }
}

- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
    id<SLPagerItem> item = self.dataSource[indexPath.row];
    NSString *identifer = [self identifierFor:indexPath];
    
    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:__sl_resue_identifier forIndexPath:indexPath];
    if (identifer) {
//        [CATransaction begin];
//        [CATransaction setDisableActions:YES];

        UIView *view = [self.reusePool dequeueReusableCellWithReuseIdentifier:identifer forIndexPath:indexPath];
        view.frame = cell.bounds;
        
        _pager_view_bind_item(view, item);
        
        [cell.contentView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
        [cell.contentView addSubview:view];
        [cell layoutIfNeeded];
        
        if(/*![self.reusePool isPrefetchedFor:indexPath]&& */ [self.delegate respondsToSelector:@selector(sl_pagerView:dequeueView:atIndexPath:withData:)]){
            [self.delegate sl_pagerView:self dequeueView:view atIndexPath:indexPath withData:item];
            [self.reusePool prefetchedFor:indexPath];
        }
        if (!self.disablePrefetch){
            [self.reusePool prefetchByVisibleIndexPaths:@[indexPath] limited:self.dataSource.count];
        }
//        [CATransaction commit];
    }
    
    return cell;
}
- (void)collectionView:(UICollectionView *)collectionView prefetchItemsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths API_AVAILABLE(ios(10.0)){
}

- (NSString *)identifierFor:(NSIndexPath *)indexPath{
    if (![self availableIndex:indexPath.row]) return nil;

    NSString *identifer = NSStringFromClass([self class]);
    id<SLPagerItem> item = self.dataSource[indexPath.row];
    
    if ([self.delegate respondsToSelector:@selector(sl_pagerView:identifierAtIndexPath:)]){
        identifer = [self.delegate sl_pagerView:self identifierAtIndexPath:indexPath];
    }else{
        if ([item respondsToSelector:@selector(classForCell)]){
            identifer = NSStringFromClass([item classForCell]);
        }
    }
    return identifer;
}
#pragma mark - Prefetch
- (void)sl_pagerPrefetchAtIndexPath:(NSIndexPath *)indexPath{
    if (![self availableIndex:indexPath.row]) return;
    
    UIView *view = [self.reusePool dequeueReusableCellWithReuseIdentifier:[self identifierFor:indexPath] forIndexPath:indexPath];
    id<SLPagerItem> item = self.dataSource[indexPath.row];
    view.frame = self.collectionView.bounds;
    if([self.delegate respondsToSelector:@selector(sl_pagerView:prefetchView:atIndexPath:withData:)]){
        [self.delegate sl_pagerView:self prefetchView:view atIndexPath:indexPath withData:item];
    }
}
- (void)sl_pagerPrefetchDidCancelled:(NSIndexPath *)indexPath{
    if (self.dataSource.count <= indexPath.row){
        return;
    }
    UIView *view = [self.reusePool reuseViewFor:indexPath];

    if([self.delegate respondsToSelector:@selector(sl_pagerView:didCancelledPrefetch:atIndexPath:withData:)]){
        [self.delegate sl_pagerView:self didCancelledPrefetch:view atIndexPath:indexPath withData:_pager_view_item_from(view)];
    }
}
#pragma mark - Data Reload
- (BOOL)disablePrefetch{
    if(!self.prefetchingEnabled) return YES;
    
    return _disablePrefetch;
}
- (void)registerData:(NSArray *)data{
    NSMutableSet *set = [NSSet setWithArray:
                         sl_array_map(sl_array_filter(data, ^BOOL(id  _Nonnull obj) {
        return [obj respondsToSelector:@selector(classForCell)];
    }), ^id _Nullable(id<SLPagerItem>  _Nonnull obj) {
        if([obj respondsToSelector:@selector(classForCell)]){
            return NSStringFromClass([obj classForCell]);
        }else{
            return NSStringFromClass([UICollectionViewCell class]);
        }
    })].mutableCopy;
    [set minusSet:self.registerSet];
    
    [set.allObjects enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL * _Nonnull stop) {
        [self.reusePool registerClass:NSClassFromString(obj)
             forCellWithReuseIdentifier:obj];
    }];
    [self.registerSet setByAddingObjectsFromSet:set];
}
- (NSArray<id<SLPagerItem>> *)dataSource{
    if (_dataSource.count == 1) {
        return @[_dataSource.firstObject, _dataSource.firstObject, _dataSource.firstObject];
    }else if (_dataSource.count == 2) {
        return [_dataSource arrayByAddingObjectsFromArray:_dataSource];
    }
    return _dataSource;
}
- (void)setDataSource:(NSArray<id<SLPagerItem>> *)dataSource{
    [self setDataSource:dataSource scrollToIndex:self.currentIndex];
}
- (void)setDataSource:(NSArray<id<SLPagerItem>> *)dataSource scrollToIndex:(NSInteger)toIndex{
    [self registerData:dataSource];
    
    sl_dispatch_main_sync_safe(^{
        self->_dataSource = dataSource;
        if (dataSource.count == 0){
            [self.reusePool clear];
        }
        [self.reusePool clearAllPrefetched];
        self.reusePool.disablePrefetched = YES;
        
        [self.collectionView reloadData];
//        [self.collectionView layoutIfNeeded];
        
        self.reusePool.disablePrefetched = NO;

        [self scrollToIndex:toIndex animated:NO];

    });
}
- (void)removeItem:(id)item animated:(BOOL)animated{
    sl_dispatch_main_sync_safe(^{
        
        void (^block)(void) = ^{
            if (![self.dataSource containsObject:item]){
                return;
            }
            
            NSInteger index = [self.dataSource indexOfObject:item];
            NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
            
            self->_dataSource = sl_array_filter(self.dataSource, ^BOOL(id  _Nonnull obj) {
                return obj != item;
            });
            [self.reusePool updateIndexPath:^NSIndexPath * _Nonnull(NSIndexPath * _Nonnull indexPath) {
                if (indexPath.row <= index){
                    return indexPath;
                }
                return [NSIndexPath indexPathForRow:indexPath.row - 1 inSection:0];
            }];
            [self.reusePool invaliteAllReuse];
            [self.collectionView deleteItemsAtIndexPaths:@[indexPath]];
        };
        if (animated){
            [self.collectionView performBatchUpdates:block completion:^(BOOL finished) {
                [self notifyObjectToIndex:self.currentIndex];
            }];
        }else{
            
            [UIView performWithoutAnimation:^{
                block();
                [self notifyObjectToIndex:self.currentIndex];
            }];
        }
        
    });
}
- (void)appendData:(NSArray *)data atPosition:(SLPagerViewDataPosition)position{
    if (data.count <= 0) return;
    
    [self registerData:data];
    
    if (!_dataSource.count){
        _dataSource = [NSMutableArray new];
    }
    self.reusePool.preferredFrame = self.bounds;
    self.reusePool.disablePrefetched = YES;

    if (position == SLPagerViewDataPositionAfter){
        _dataSource = [_dataSource arrayByAddingObjectsFromArray:data];
    }else{
        [self.reusePool clearAllPrefetched];
        _dataSource = [data arrayByAddingObjectsFromArray:_dataSource];
        _currentIndex = data.count + self.currentIndex;
        [self.reusePool updateIndexPath:^(NSIndexPath * _Nonnull indexPath) {
            return [NSIndexPath indexPathForRow:indexPath.row + data.count inSection:indexPath.section];
        }];
    }
    
    [self.collectionView reloadData];
//    [self.collectionView layoutIfNeeded];
    self.reusePool.disablePrefetched = NO;

    if (!self.isScrolling){
        [self scrollToIndex:self.currentIndex animated:NO];
    }
}
#pragma mark - UIScrollView
- (void)changeToFrame:(CGRect)frame{
    [CATransaction begin];
    [CATransaction setDisableActions:YES];
    
    NSInteger index = self.currentIndex;
    if (!CGRectEqualToRect(self.collectionView.frame, frame)){
        self.collectionView.frame = frame;
        [self.collectionView reloadData];
        [self.collectionView layoutIfNeeded];
        
        [self scrollToIndex:index animated:NO];
    }
  
    [CATransaction commit];
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
    CGFloat tmp = self.collectionView.contentOffset.y/CGRectGetHeight(self.collectionView.bounds);
    NSInteger index = roundf(self.collectionView.contentOffset.y/CGRectGetHeight(self.collectionView.bounds) );
    if (fabs(tmp - index) < 0.1){
        self.currentIndex = MAX(0, MIN(index%self.dataSource.count, self.dataSource.count - 1));
    }

//    [self autoScrollToRightPosition];
}

- (void)scrollToNextIndex{
    [self scrollToIndex:self.currentIndex + 1 animated:YES];
}
- (void)scrollToIndex:(NSInteger)index animated:(BOOL)animated{
    if (self.dataSource.count == 0) return;

    NSInteger toIndex = MIN(MAX(0, index), self.dataSource.count - 1);

    self.disablePrefetch = YES;
    [self.collectionView performBatchUpdates:^{
        [self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:toIndex inSection:0] atScrollPosition:UICollectionViewScrollPositionTop animated:animated];
    } completion:^(BOOL finished) {
        NSTimeInterval interval = [[NSDate date] timeIntervalSince1970];
        while (self.currentIndex != toIndex && fabs([[NSDate date] timeIntervalSince1970] - interval) < 3) {
            [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.2]];
        }
        self.disablePrefetch = NO;
        [self notifyObjectToIndex:self.currentIndex];
        if(!self.disablePrefetch){
            [self.reusePool prefetchByVisibleIndexPaths:@[[NSIndexPath indexPathForRow:self.currentIndex inSection:0]] limited:self.dataSource.count];
        }

    }];
    
}
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset{
}
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
    if (!decelerate) {
        if ([self scrollStopped]) {
            [self scrollViewDidEndScroll];
        }
    }
}

-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    if ([self scrollStopped]) {
        [self scrollViewDidEndScroll];
    }
}
- (BOOL)scrollStopped{
    return !self.collectionView.tracking && !self.collectionView.dragging && !self.collectionView.decelerating;
}
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView{
    [self scrollViewDidEndScroll];
}
- (void)didMoveToWindow{
    if (!self.window){
        [self scrollViewDidEndScroll];
    }
}
- (void)scrollViewDidEndScroll {
    [self notifyObjectToIndex:self.currentIndex];
}

- (void)setCurrentIndex:(NSInteger)index{
    if (![self availableIndex:index]) return;
    
    [self performFromTo:^{
        if (self->_currentIndex == index) return NO;

        [self willChangeValueForKey:@"index"];
        self->_currentIndex = index;
        [self didChangeValueForKey:@"index"];
        return YES;
    }];
    
}
- (SLPagerObject *)pagerObjectFor:(NSInteger)index{
    if (![self availableIndex:index]) return nil;
    
    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
    UIView *view = [self.reusePool reuseViewFor:indexPath];
    id item = self.dataSource[index];
    return view?pagerObjectFrom(view, indexPath, item):nil;
}
- (void)performFromTo:(BOOL (^)(void))block{
    
    if (block && block()){
        [self notifyObjectToIndex:self.currentIndex];
    }
}
- (void)autoScrollToRightPosition{
    BOOL scrolling = ![self scrollStopped];

    if(!scrolling){
        uint64_t y = self.collectionView.contentOffset.y;
        uint64_t h = CGRectGetHeight(self.bounds);
        
        if (y > 0 && h > 0 && self.dataSource.count > 0){
            int64_t deltaY = self.currentIndex * h - y % (h * self.dataSource.count);
            if (deltaY != 0){
                y += deltaY;
                CGPoint point = self.collectionView.contentOffset;
                point.y = y;

                [self.collectionView setContentOffset:point];
            }
        }
    }
}
- (void)notifyObjectToIndex:(NSInteger)index{
    dispatch_async(dispatch_get_main_queue(), ^{
        BOOL scrolling = ![self scrollStopped];
        
        SLPagerObject *to = [self pagerObjectFor:index];
        if(!to) return;
        
        to.scrolling = scrolling;
        if (![self.prevObj isEqual:to]){
            if ([self.delegate respondsToSelector:@selector(sl_pagerView:didFrom:to:scrolling:)]){
                [self.delegate sl_pagerView:self didFrom:self.prevObj to:to scrolling:scrolling];
            }
            self.prevObj = to;
        }
        [self autoScrollToRightPosition];
       
    });
}
- (BOOL)availableIndex:(NSInteger)index{
    return index >= 0 && index < self.dataSource.count;
}
- (UIView*)viewAtIndex:(NSInteger)index{
    if (![self availableIndex:index]) return nil;
    
    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
    NSString *identifier = [self identifierFor:indexPath];
    if(!identifier) return nil;
    
    return [self.reusePool dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
}

- (NSIndexPath *)indexForView:(UIView *)view{
    return [self.reusePool indexPathForReuseView:view];
}


/*
 利用runtime机制实现view监听controller的生命周期
 **/

static NSString *const _sl_pager_prefix = @"__SL_class_9alah833_";
const char * const _sl_pagerview_ass = "_sl_pagerview_asso_9alah833";
NSString *const _sl_pager_dealloc = @"__dealloc_9alah833_";

Class SL_class(id self,SEL _cmd){
    return class_getSuperclass(object_getClass(self));
}
static __unused void sl_dealloc(id self,SEL _cmd){
    Class superClass = [self class];
    object_setClass(self, superClass);
}

#define __sl_life_cycle_declare(func)\
void _sl_##func(id self,SEL _cmd, BOOL value){\
    void (*sl_msgSendSuper)(void *,SEL, BOOL) = (void *)objc_msgSendSuper;\
    struct objc_super superStruct = {\
        .receiver = self,\
        .super_class = class_getSuperclass(object_getClass(self)),\
    };\
    sl_msgSendSuper(&superStruct,_cmd, value);\
    id<SLPagerLifeCycle> (^block)(void) = objc_getAssociatedObject(self, _sl_pagerview_ass);\
    if (block){\
        id<SLPagerLifeCycle> cycle = block();\
        if (cycle && [cycle respondsToSelector:_cmd]){\
            [cycle func:value];\
        }\
    }\
}

__sl_life_cycle_declare(viewWillAppear)
__sl_life_cycle_declare(viewDidAppear)
__sl_life_cycle_declare(viewWillDisappear)
__sl_life_cycle_declare(viewDidDisappear)


#define __sl_life_cycle_synthesize(func)\
{\
    SEL sel = NSSelectorFromString(@""#func":");\
    Method method = class_getInstanceMethod([controller class], sel);\
    const char *types = method_getTypeEncoding(method);\
    class_addMethod(cls, sel, (IMP)_sl_##func, types);\
}\


- (void)observeOnViewController:(UIViewController *)controller{
    
    if ([controller isKindOfClass:[UIViewController class]]){
        [self createChildClass:controller];
        
        ///实现OBJC_ASSOCIATION weak方式，避免因为内存释放导致的exec_bad_access
        __weak id<SLPagerLifeCycle> weakSelf = self;
        id<SLPagerLifeCycle> (^block)(void)= ^{
            return weakSelf;
        };
        objc_setAssociatedObject(controller, _sl_pagerview_ass, block, OBJC_ASSOCIATION_COPY);
    }
}
- (Class)createChildClass:(UIViewController *)controller{
    NSString *oldClass = NSStringFromClass([controller class]);
    NSString *newClass = [_sl_pager_prefix stringByAppendingString:oldClass];
    Class cls = NSClassFromString(newClass);
    if (cls == NULL){
        cls = objc_allocateClassPair([controller class], newClass.UTF8String, 0);
        objc_registerClassPair(cls);
        SEL classSEL = NSSelectorFromString(@"class");
        ///给当前类 添加Method  关联sel
        Method classMethod = class_getInstanceMethod([self class], classSEL);
        const char *classTypes = method_getTypeEncoding(classMethod);
        class_addMethod(cls, classSEL, (IMP)SL_class, classTypes);
        
        __sl_life_cycle_synthesize(viewWillAppear);
        __sl_life_cycle_synthesize(viewDidAppear);
        __sl_life_cycle_synthesize(viewWillDisappear);
        __sl_life_cycle_synthesize(viewDidDisappear);
    }
    
    object_setClass(controller, cls);
    return cls;
}

#pragma mark - LifeCycle
- (void)viewWillAppear:(BOOL)animated{
}
- (void)viewDidAppear:(BOOL)animated{
}
- (void)viewWillDisappear:(BOOL)animated{
}
- (void)viewDidDisappear:(BOOL)animated{
}

@end
